﻿using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Net;
using System.IO;
using System.ServiceModel.Description;

namespace WcfMasterSlaveElectionService
{
    /// <summary>
    /// Implementation of the MasterSlaveElectionService interface
    /// </summary>
    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.Single, // allows to pass in instance of the MasterElectionService class to the ServiceHost
        ConcurrencyMode = ConcurrencyMode.Multiple,        // allows for parallel processing
        UseSynchronizationContext = false
        )]
    public class MasterElectionService : IMasterElectionService
    {
        #region Events
        /// <summary>
        /// NewMaster event is fired when a new master is selected
        /// </summary>
        public event EventHandler<MasterSelectionEventArgs> NewMaster;
        public event EventHandler<ExpectMasterEventArgs> ExpectRole;
        #endregion

        #region Private members
        /// <summary>
        /// Keep track of the current selected master
        /// </summary>
        private InstanceEndpoint _cachedMaster;
        /// <summary>
        /// Reference to the WCF host
        /// </summary>
        private readonly ServiceHost _host;
        /// <summary>
        /// List of instances 
        /// </summary>
        private readonly List<string> _electionList = null;
        /// <summary>
        /// Synchronization object
        /// </summary>
        private readonly object _masterLock = new object();
        /// <summary>
        /// true: another instance indicates that a master has already been selected. Stop our election.
        /// false: according to the instance asked, no other instance is master. Continue our election.
        /// </summary>
        private volatile bool _masterAlreadySelected = false;
        /// <summary>
        /// true: we are running an election
        /// false: we are not running an election
        /// </summary>
        private volatile bool _isRunningElection = false;
        /// <summary>
        /// true: stop the election
        /// fasle: continue the election
        /// </summary>
        private volatile bool _stopElection = false;
        /// <summary>
        /// Interface for logging
        /// </summary>
        private readonly ILog _logger;
        /// <summary>
        /// Interface for endpoint provider
        /// </summary>
        private readonly IEndpointProvider _endpointProvider = null;
        /// <summary>
        /// Id of the service. 
        /// </summary>
        private readonly string _serviceId;

        private readonly Watchdog _watchdog;
        #endregion

        #region Properties
        /// <summary>
        /// Holds the current master
        /// </summary>
        public InstanceEndpoint Master { get { return _cachedMaster; } }

        /// <summary>
        /// Gets the id of the current service/instance
        /// </summary>
        public string GetHostId
        {
            get { return _serviceId; }
        }

        /// <summary>
        /// Gets the Id of the master
        /// </summary>
        /// <returns></returns>
        public string GetMasterId()
        {
            if (Master == null)
            {
                return null;
            }
            return Master.Id;
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Instantiates the service host for master election service
        /// </summary>
        /// <param name="endpointName"></param>
        public MasterElectionService(string serviceId, IEndpointProvider endpointProvider, ILog logger)
        {
            _endpointProvider = endpointProvider;
            _isRunningElection = false;
            _logger = logger;
            _electionList = new List<string>();
            _serviceId = serviceId;

            // Create URI for the endpoint of he current role instance
            var hostUri = new Uri(endpointProvider.GetUriForRoleIntance(serviceId));

            // Create a service host
            _host = new ServiceHost(this, hostUri);

            // Create a binding for the endpoint
            NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, false);
            binding.MaxConnections = 30;

            // Add the endpoint to the host
            _host.AddServiceEndpoint(typeof(IMasterElectionService), binding, hostUri);

            _watchdog = new Watchdog(this, _logger);
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Opens the conenction to service host for the client to listen 
        /// </summary>
        public void StartListen()
        {
            //http://stackoverflow.com/questions/2977630/wcf-instance-already-exists-in-counterset-error-when-reopening-servicehost
            GC.Collect();
            GC.WaitForPendingFinalizers();

            try
            {
                while (_host.State != CommunicationState.Opened)
                {
                    _host.Open();
                }
            }
            catch (Exception ex)
            {
                _logger.LogMessage(ex, "Error opening host on URI", _host.BaseAddresses[0].AbsoluteUri);
                throw;
            }

            _logger.LogMessage("Host listening on {0}", _host.BaseAddresses[0].AbsoluteUri);
            _watchdog.Start();

        }

        /// <summary>
        /// Closes the connection. This host will no longer take part in the Master election process.
        /// If the service is master, then a new master will be assigned.
        /// </summary>
        public void StopListen()
        {
            _watchdog.Stop();
            try
            {
                while (_host.State != CommunicationState.Closed)
                {
                    _logger.LogMessage("Host stops listening on {0}", _host.BaseAddresses[0].AbsoluteUri);
                    _host.Close();
                    _logger.LogMessage("Host stopped");
                }
            }
            catch (Exception ex)
            {
                _logger.LogMessage("Error closing host");
            }

            if (IsMaster())
            {
                AssignNewMaster();
            }

            //http://stackoverflow.com/questions/2977630/wcf-instance-already-exists-in-counterset-error-when-reopening-servicehost
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        /// <summary>
        /// starts and ensures the election of a master among the master slave instances
        /// </summary>
        public void EnsureMasterElected()
        {
            try
            {
                string msg = "Determining master";
                _logger.LogMessage(msg);

                if (this.RefreshMasterEndpoint())
                {
                    if (this.Master != null)
                    {
                        msg = string.Format("Elected master: {0}", this.Master.Id);
                    }
                    else
                    {
                        msg = string.Format("Election is running by other instance. Waiting for the result of the election.");
                    }
                }
                else
                {
                    msg = "No election performed, master still alive";
                }
                _logger.LogMessage(msg);
            }
            catch (Exception ex)
            {
                _logger.LogMessage(ex, "Error in EnsureMasterElected method.");
            }
        }

        // return true if cached master is valid
        // return false if cached is updated for new master
        public bool ValidateMaster()
        {
            bool running = false;
            _logger.LogMessage("In Validate Master");
            try
            {
                // check against cached Master node first
                if (Master != null)
                {
                    _logger.LogMessage("ValidateMaster: Cached Master: {0}", Master.Uri);

                    try
                    {
                        ServiceEndpoint serviceEndpointData = GetClientForRoleInstance(Master.Id);
                        IsMasterServiceCaller serviceCaller = new IsMasterServiceCaller(serviceEndpointData);
                        serviceCaller.Execute();
                        running = serviceCaller.IsMaster;
                        _logger.LogMessage("ValidateMaster: Cached Master IsRunning: {0}, {1}", Master.Id, running.ToString());
                    }
                    catch (Exception)
                    {
                        _logger.LogMessage("Master offline:{0}", Master.Id);
                        _cachedMaster = null;
                    }
                }
                else
                {
                    _logger.LogMessage("ValidateMaster: Cached Master: NULL");
                }
            }
            catch (Exception ex)
            {
                _logger.LogMessage("ValidateMaster:Error: {0}, {1}", ex.Message, ex.StackTrace);
            }
            _logger.LogMessage("End of ValidateMaster: Master Running: {0}", running.ToString());
            return running;
        }

        /// <summary>
        ///  The master is shutting down. Selects a random client to force an election
        /// </summary>
        public void AssignNewMaster()
        {
            Dictionary<string, ServiceEndpoint> endpoints = GetAllCurrentEndpoints();

            if (endpoints.Count < 2)
            {
                _logger.LogMessage("No instance to assign the master role to");
                return;
            }

            // Select the index of our service in the list of endpoints to get the next endpoint
            // (or if the current is the last, the first) in the list
            int index = 0;
            foreach (KeyValuePair<string, ServiceEndpoint> kvp in endpoints)
            {
                if (kvp.Key == _serviceId)
                {
                    break;
                }
                index++;
            }
            // Advance to the next client in th list
            index++;

            // If we have passed the end of the list (meaning our _serviceId was the last in the list)
            // we take the first client in the list
            if (index >= endpoints.Count)
            {
                index = 0;
            }

            ServiceEndpoint serviceEndpointData = endpoints.ElementAt(index).Value;

            _logger.LogMessage("Assigning new master: " + serviceEndpointData.Uri);
            new BecomeMasterServiceCaller(serviceEndpointData).Execute();
            _cachedMaster = null;
        }

        public bool IsWatchdogAlive()
        {
            return _watchdog.IsWatchdogAlive();
        }

        /// <summary>
        /// Let the process know whether the watchdag expects it to be master of slave, just for checking...
        /// </summary>
        /// <param name="isMaster"></param>
        internal void ExpectMaster(bool isMaster)
        {
            EventHandler<ExpectMasterEventArgs> listeners = ExpectRole;
            if (listeners != null)
            {
                ExpectMasterEventArgs e = new ExpectMasterEventArgs()
                {
                    ExpectedRoleIsMaster = isMaster
                };
                listeners(this, e);
            }
        }
        #endregion

        #region Private methodes
        /// <summary>
        /// update master's endpoint 
        /// called during role start or whenever it's offline
        /// </summary>
        /// <returns>true if master endpoint is updated
        /// false if cached master is still running</returns>
        private bool RefreshMasterEndpoint()
        {
            if (ValidateMaster())
            {
                return false;
            }

            // master is not valid, start MasterElection process
            // note: multiple instances can start election simutaneously
            StartElection();

            return true;
        }

        /// <summary>
        /// Gets all service endpoints taking part in the master slave election
        /// </summary>
        /// <returns></returns>
        private Dictionary<string, ServiceEndpoint> GetAllCurrentEndpoints()
        {
            Dictionary<string, ServiceEndpoint> clients = new Dictionary<string, ServiceEndpoint>();

            foreach (KeyValuePair<string, string> kvp in _endpointProvider.GetCurrentEndpoints())
            {
                clients.Add(kvp.Key, new ServiceEndpoint(kvp.Value, _logger));
            }

            return clients;
        }

        /// <summary>
        /// Gets the service endpoint corresponding to the RoleInstanceId
        /// </summary>
        /// <param name="roleInstanceId"></param>
        /// <returns></returns>
        private InstanceEndpoint GetEndPointOfRoleIntance(string roleInstanceId)
        {
            return _endpointProvider.GetEndPointOfRoleIntance(roleInstanceId);
        }

        private ServiceEndpoint GetClientForRoleInstance(string roleInstanceId)
        {
            string uri = _endpointProvider.GetUriForRoleIntance(roleInstanceId);
            return new ServiceEndpoint(uri, _logger);
        }

        /// <summary>
        /// Starts and proposes election for a master among the services
        /// </summary>
        private void StartElection()
        {
            try
            {
                _logger.LogMessage("In StartElection");
                _masterAlreadySelected = false;
                _isRunningElection = true;   // Indicates we are running an election
                _stopElection = false;      // Value of true: another client is also running the election
                _electionList.Clear();
                AddElectionListEntry(GetHostId); // this server does participate in the election with MasterElectionValue = MasterId

                Dictionary<string, ServiceEndpoint> electionServices = GetAllCurrentEndpoints();
                _logger.LogMessage("Instances participating in election: " + electionServices.Count.ToString());
                if (electionServices.Count < 2)
                {
                    // Become master. We are alone.
                    this.BecomeMaster();
                    _isRunningElection = false;
                    return;
                }

                AutoResetEvent[] waitHandles = new AutoResetEvent[electionServices.Count - 1];
                for (int i = 0; i < waitHandles.Length; i++)
                {
                    waitHandles[i] = new AutoResetEvent(false);
                }

                int j = 0;
                foreach (string instanceID in electionServices.Keys)
                {
                    if (instanceID != _serviceId)
                    {
                        // start proposing in parallel
                        ThreadPool.QueueUserWorkItem(new WaitCallback(RequestMasterElectionIdThread), new ThreadInfo(electionServices[instanceID], waitHandles[j]));
                        j++;
                    }
                }

                _logger.LogMessage("All RequestMasterElectionId requests are queued. Number of requests: " + j.ToString());
                WaitHandle.WaitAll(waitHandles, TimeSpan.FromMinutes(2));
                _logger.LogMessage("All RequestMasterElectionId requests have returned.");

                _isRunningElection = false;

                // _masterAlreadySelected: true means that a master was already selected, we did not choose a new master
                // _stopElection: true means that another service is running the election, we don't have to continue our election
                if (!_masterAlreadySelected && !_stopElection)
                {
                    // No master found and no other service is running the election
                    // select a master now
                    DeclareVictory();
                }

                _logger.LogMessage("End of StartElection");
            }
            catch (Exception ex)
            {
                _logger.LogMessage(ex, "StartElection:Error");
            }
        }

        /// <summary>
        /// Thread to call other services in parallel to take part in the election.
        /// A service takes part when calling CastElection on the current 
        /// </summary>
        /// <param name="o"></param>
        private void RequestMasterElectionIdThread(object o)
        {
            const int retryCount = 3;
            ThreadInfo threadInfo = (ThreadInfo)o;
            ServiceEndpoint serviceEndpointData = threadInfo.Client;

            try
            {
                int count = 0;
                while (count < retryCount)
                {
                    _logger.LogMessage("RequestMasterElectionId, run: ", count.ToString());
                    try
                    {
                        if (!_masterAlreadySelected && !_stopElection)
                        {
                            // protect since peer instance may not be up at this moment.
                            // If the peer is not up, then it will not take part in the election
                            RequestMasterElectionIdServiceCaller serviceCaller = new RequestMasterElectionIdServiceCaller(serviceEndpointData);
                            serviceCaller.Execute();
                            RequestMasterElectionIdResult result = serviceCaller.RequestMasterElectionIdResult;

                            if (result != null)
                            {
                                // Check if the client is also running an election proces. If so, then check whose MasterEletionId has the lowest value.
                                // If the client has a lower value, then stop our election.
                                if (result.IsRunningElection && string.CompareOrdinal(result.Id, _serviceId) > 0)
                                {
                                    _stopElection = true;
                                }

                                if (result.Master != null)
                                {
                                    lock (_masterLock)
                                    {
                                        Dictionary<string, ServiceEndpoint> electionServices = GetAllCurrentEndpoints();
                                        if (!electionServices.ContainsKey(result.Master))
                                        {
                                            _logger.LogMessage("Master {0} not present in list of endpoints!", result.Master);
                                            foreach (string knownEndpoint in electionServices.Keys)
                                            {
                                                _logger.LogMessage(knownEndpoint);
                                            }
                                        }
                                        else
                                        {
                                            ServiceEndpoint serviceEndpointDataOfMaster = electionServices[result.Master];
                                            IsMasterServiceCaller masterServiceCaller = new IsMasterServiceCaller(serviceEndpointDataOfMaster);
                                            masterServiceCaller.Execute();

                                            if (masterServiceCaller.IsMaster)
                                            {
                                                _masterAlreadySelected = true;
                                                _cachedMaster = GetEndPointOfRoleIntance(result.Master);
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    AddElectionListEntry(result.Id);
                                }
                            }
                        }
                        break;
                    }
                    catch (Exception ex)
                    {
                        _logger.LogMessage(ex, "Error while running RequestMasterElectionId");
                        count++;
                        Thread.Sleep(1000);
                    }
                }
                if (count == retryCount)
                {
                    _logger.LogMessage(" failed: ProposeElection to {0}", serviceEndpointData.Uri);
                }
            }
            finally
            {
                _logger.LogMessage("Finished RequestMasterElectionId.");
                threadInfo.AutoReset.Set();
            }
        }

        private void AddElectionListEntry(string hostInstanceId)
        {
            lock (_masterLock)
            {
                if (!_electionList.Contains(hostInstanceId))
                {
                    _electionList.Add(hostInstanceId);
                }
            }
        }

        /// <summary>
        /// Declare the winner of master election and broadcasts 
        /// the victory
        /// </summary>
        private void DeclareVictory()
        {
            _logger.LogMessage("Select master from following instances:");
            foreach (string id in _electionList)
            {
                _logger.LogMessage("\t" + id);
            }

            List<string> chain = _electionList;
            if (chain == null || chain.Count == 0)
            {
                return;
            }
            
            OnNewMaster(chain[0]); // Select the new master!!!!
            BroadcastNewMaster();
        }

        private void BroadcastNewMaster()
        {
            Dictionary<string, ServiceEndpoint> electionServices = GetAllCurrentEndpoints();
            foreach (string instanceID in electionServices.Keys)
            {
                if (instanceID != _serviceId)
                {
                    // use thread to do parallel broadcasting
                    BroadcastVictoryThread(instanceID, Master.Id);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="instanceID"></param>
        /// <param name="chain"></param>
        private void BroadcastVictoryThread(string instanceID, string newMaster)
        {
            Thread sender = new Thread(delegate()
            {
                ServiceEndpoint serviceEndpointData = GetClientForRoleInstance(instanceID);
                new BroadcastVictoryServiceCaller(serviceEndpointData, newMaster).Execute();
            });
            sender.Start();
        }

        /// <summary>
        /// Gets fired on election of new master
        /// </summary>
        /// <param name="chain"></param>
        private void OnNewMaster(string newMaster)
        {

            if (Master != null)
            {
                if (Master.Id == newMaster)
                {
                    // master is unchanged, do nothing
                    return;
                }
            }

            _cachedMaster = GetEndPointOfRoleIntance(newMaster);
            if (NewMaster != null)
            {
                MasterSelectionEventArgs e = new MasterSelectionEventArgs()
                {
                    MasterId = newMaster
                };
                NewMaster(this, e);
            }
        }

        #endregion

        #region IMasterElectionService Members
        /// <summary>
        /// Receives the broadcast message indicating the new master
        /// </summary>
        /// <param name="chain"></param>
        public void BroadcastVictory(string newMaster)
        {
            _stopElection = true;//stop our election proces if we are running an election
            OnNewMaster(newMaster);
        }

        /// <summary>
        /// Receives a message requesting if the current instance is the master instance
        /// </summary>
        /// <returns></returns>
        public bool IsMaster()
        {
            if (Master == null)
            {
                return false;
            }
            return Master.Id == GetHostId;
        }

        /// <summary>
        /// Receives a message from the master when it is shutting down, to become the new master
        /// </summary>
        public void BecomeMaster()
        {
            _logger.LogMessage("I have been assigned the new master!");
            OnNewMaster(GetHostId);
            BroadcastNewMaster();
        }

        /// <summary>
        /// Requests the Id this instance uses for election
        /// </summary>
        /// <returns></returns>
        public RequestMasterElectionIdResult RequestMasterElectionId()
        {
            RequestMasterElectionIdResult result = new RequestMasterElectionIdResult();

            // The id this instance uses for the election
            result.Id = _serviceId;

            // This value contains the Id of the master is a master is already elected
            result.Master = GetMasterId();

            // Let the caller know whether we a also running an election
            result.IsRunningElection = _isRunningElection;

            return result;
        }
        #endregion

    }
}
